﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ZetaLongPaths;

namespace FolderSynchronizer
{
	/// <summary>
	/// Вспомогательный класс копирования файлов.
	/// </summary>
	internal class SynchronizersUtility
	{
		/// <summary>
		/// Файлы для копирования.
		/// </summary>
		public List<FileInfo> Files { get; private set; }

		private readonly string sourcePath;
		private readonly string targetPath;
		private readonly SynchronizeProgress bar;
		private readonly bool delOn;
		private readonly bool delete;
		private readonly bool newFile;
		private readonly Action throwIfCancellationRequested;
		private readonly string[] excludeFiles;
		private readonly List<FileInformation> filesForCopy;
		private readonly DirectoryInfo dir;
	    private readonly bool cancell;
        private readonly bool firstStage;
        private int num;

		/// <summary>
		/// Конструктор.
		/// </summary>
		/// <param name="sourcePath">Путь к исходной папке.</param>
		/// <param name="targetPath">К конечной папке.</param>
		/// <param name="settings">Конфигурация копировования.</param>
        /// <param name="delOn">Задействован ли делегат, true-задействован, по умолчанию не задействован.</param>
		/// <param name="delete">Удалять, или не удалять файлы после копирования. по умолчанию -false.</param>
		/// <param name="newFile">Использовать ли заранее полученный список для копирования или получать файлы из исходной папки. По умолчанию false.</param>
		/// <param name="cancell">Возможна ли отмена операции.</param>
		/// <param name="firstStage">Копируется ли в промежуточную.</param>
		public SynchronizersUtility(string sourcePath, string targetPath, SynchronizerSettings settings, bool delOn = false, bool delete = false, bool newFile = false, bool cancell = false, bool firstStage = false)
		{
			this.sourcePath = sourcePath;

			if (!Directory.Exists(sourcePath))
			{
				throw new DirectoryNotFoundException(string.Format("Каталог {0} не найден.", sourcePath));
			}

			this.targetPath = targetPath;
			this.delOn = delOn;
			this.delete = delete;
			this.newFile = newFile;
            bar = settings.Bar;
		    throwIfCancellationRequested = settings.ThrowIfCancellationRequested;
		    excludeFiles = settings.ExcludeFiles;
		    filesForCopy = settings.FilesForCopy;
			Files = new List<FileInfo>();
			dir = new DirectoryInfo(this.sourcePath);
		    this.cancell = cancell;
            this.firstStage = firstStage;
		}


		/// <summary>
		/// Метод копирования из одной папки в другую.
		/// </summary>
		public void Compute()
		{
			CreateTargetFolder();
			ReceiptAndProcessingOfFiles();
		    num = Files.Count();
			ProcessOfCopy();
		}

		private void CreateTargetFolder()
		{
			if (!Directory.Exists(targetPath))
			{
				Directory.CreateDirectory(targetPath);
			}
		}

		private void ReceiptAndProcessingOfFiles()
		{
			ExtractFilesToList();
			DeleteExludeFilesFromList();
		}

		private void ProcessOfCopy()
		{
			SeriesOfCreateFolder();
			SeriesOfCopyFile();
		}

		private void SeriesOfCreateFolder()
		{
			DirectoryInfo[] dirs = dir.GetDirectories(".", SearchOption.AllDirectories);
			foreach (DirectoryInfo dr in dirs)
			{
				ZlpIOHelper.CreateDirectory(Path.Combine(this.targetPath, dr.FullName.Substring(this.dir.FullName.IndexOf(this.dir.Name, System.StringComparison.Ordinal) + this.dir.Name.Count() + 1)));
			}
		}

		private void SeriesOfCopyFile()
		{
			int k = 0;
			foreach (FileInfo file in this.Files)
			{
                if (throwIfCancellationRequested != null && cancell)
                {
                    ActionHandling();
                }

				ZlpIOHelper.CopyFile(file.FullName, Path.Combine(targetPath, file.FullName.Substring(this.dir.FullName.IndexOf(this.dir.Name, System.StringComparison.Ordinal) + this.dir.Name.Count() + 1)), true);
				IfDeleteFile(file);
				k = ConnectDelegate(k, file);
			}
		}

		private void ActionHandling()
		{
		    throwIfCancellationRequested();
		}

		private void IfDeleteFile(FileInfo file)
		{
			if (delete)
			{
				file.Delete();
			}
		}

		private int ConnectDelegate(int k, FileInfo file)
		{
			if (delOn)
			{
                if (firstStage)
                {
                    k++;
                    bar(k, file.Name, file.DirectoryName, 2 * num);
                }
                else
                {
                    k++;
                    bar((int)(k + num), file.Name, file.DirectoryName, 2 * num);
                }
			}

			return k;
		}

		private void DeleteExludeFilesFromList()
		{
			if (excludeFiles == null) return;
			foreach (string name in excludeFiles)
			{
				for (int i = 0; i < Files.Count(); i++)
				{
					string fileName = Files[i].FullName.Substring(dir.FullName.IndexOf(dir.Name, System.StringComparison.Ordinal) + dir.Name.Count() + 1);
					if (fileName == name)
					{
						Files.RemoveAt(i);
					}
				}
			}
		}

		private void ExtractFilesToList()
		{
			if (newFile && (filesForCopy != null))
			{
				foreach (FileInformation file in filesForCopy)
				{
					Files.Add(file.FileInfo);
				}
			}
			else
			{
				Files = dir.GetFiles(".", SearchOption.AllDirectories).ToList();
			}
		}
	}
}
